# coding=utf-8
#Boa:Frame:Frame1

import wx, sys, time, datetime, ctypes, re, winpcapy, os, traceback, mp3play
from wx.lib.anchors import LayoutAnchors

from capture import PacketCapture
from threading import *
from listctrl import ListCtrlPanel
from ctrlStates import *
from winRegistry import WindowsRegistry
from bnetParse import *

def create(parent):
    return Frame1(parent)

[wxID_FRAME1, wxID_FRAME1BCLEARFILTERLIST, wxID_FRAME1BCLEARMAINLIST, 
 wxID_FRAME1BCOPYFILTER, wxID_FRAME1BFILTER, wxID_FRAME1BSTART, 
 wxID_FRAME1BSTOP, wxID_FRAME1CAPTUREBOX, wxID_FRAME1CBCLEARLISTONFILTER, 
 wxID_FRAME1CBCLEAROLDGAMES, wxID_FRAME1CBCLIPBOARDNEWESTFOUND, 
 wxID_FRAME1CBSOUND, wxID_FRAME1FILTERBOX, wxID_FRAME1FILTEREDLISTPANEL, 
 wxID_FRAME1FULLLISTPANEL, wxID_FRAME1LEFTPANEL, wxID_FRAME1LIFACE, 
 wxID_FRAME1LIMATCHTYPE, wxID_FRAME1MAINPANEL, wxID_FRAME1NOTEBOOK, 
 wxID_FRAME1RIGHTPANEL, wxID_FRAME1STATICBOX1, wxID_FRAME1STATICTEXT1, 
 wxID_FRAME1STATICTEXT2, wxID_FRAME1TCURRENTFILTEREDGAME, wxID_FRAME1TFILTER, 
 wxID_FRAME1TLOG, wxID_FRAME1TOLDGAMEAGE, 
] = [wx.NewId() for _init_ctrls in range(28)]

class Logger(object):
    def __init__(self, txtCtrl):
        self.stdout = sys.stdout
        sys.stdout = self
        self.ctrl = txtCtrl
        
    def write(self, val):
        #self.ctrl.SetValue(val + self.ctrl.GetValue())
        self.ctrl.AppendText(val)
        self.stdout.write(val)


class WC3PacketCapture(PacketCapture):
    capturePorts = [6112]
    
    def __init__(self, wnd):
        self.notify_window = wnd
        self.buffer = ""
        
    def dataReceived(self, packet):
        data = packet['data'] if packet else ""
        outgoing = packet['outgoing'] if packet else False
        
        self.buffer += data
        buffer = self.buffer
        if len(buffer) < 4:
            #wait for more data
            return
            
        if buffer[0] != BNET_HEADER_CONSTANT:
            raise ProtocolError("Invalid header constant: %r" % buffer[0])
        packetLen = unpack("<H", buffer[2:4])[0]
        
        if len(buffer) < packetLen:
            #wait for more data
            return
            
        packetType = ord(buffer[1])
        packetLen = packetLen - 4 #header size
        buffer = buffer[4:]
        packet = buffer[:packetLen]
        self.buffer = buffer[packetLen:]
        if not outgoing:
            self.rawPacketReceived(packetType, packet)
        #else:
        #    self.rawPacketSent(packetType, packet)
        
        #cheat reentry without data to see if there are more packets
        self.dataReceived("")
        
    def rawPacketReceived(self, packetType, rawPacket):
        if packetType == SID_GETADVLISTEX:
            packet = GETADVLISTEX.parse(rawPacket)
            self.announceReceived(packet)
                
    def rawPacketSent(self, packetType, rawPacket):
        """ for debugging/capturing """
        if packetType == SID_GETADVLISTEX:
            print repr(rawPacket)
                
    def announceReceived(self, packet):
        if packet.NumGames:
            wx.PostEvent(self.notify_window, AnnouncedEvent(packet.NumGames))
            for game in packet.data:
                wx.PostEvent(self.notify_window, GameFoundEvent(game))
                
            wx.PostEvent(self.notify_window, AnnounceCompleteEvent(packet.NumGames))
                        
    def filter_tcp_packet(self, tcp_packet):
        """
        TCP packet from WC3 server port?
        """
        if tcp_packet['source_port'] in self.capturePorts or tcp_packet['dest_port'] in self.capturePorts:
            tcp_packet['outgoing'] = False
            if tcp_packet['dest_port'] in self.capturePorts:
                tcp_packet['outgoing'] = True
            return True
    
        return False
    
    def handle_tcp_packet(self, tcp_packet):
        """ Handle an intercepted TCP packet. """
        if not self.filter_tcp_packet(tcp_packet):
            return
        
        try:
            self.dataReceived(tcp_packet)
        except (ProtocolError, Exception), e:
            #print "Error:", traceback.print_exc()
            #print e, traceback.print_exc()
            self.buffer = ""
        return

# Define notification event for thread completion
EVT_RESULT_ID = wx.NewId()

def EVT_RESULT(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_RESULT_ID, func)
    
EVT_GAMEFOUND_ID = wx.NewId()

def EVT_GAMEFOUND(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_GAMEFOUND_ID, func)
    
EVT_ANNOUNCED_ID = wx.NewId()

def EVT_ANNOUNCED(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_ANNOUNCED_ID, func)
    
EVT_ANNOUNCECOMPLETE_ID = wx.NewId()

def EVT_ANNOUNCECOMPLETE(win, func):
    """Define Result Event."""
    win.Connect(-1, -1, EVT_ANNOUNCECOMPLETE_ID, func)

class ResultEvent(wx.PyEvent):
    """Simple event to carry arbitrary result data."""
    def __init__(self, data):
        """Init Result Event."""
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_RESULT_ID)
        self.data = data
        
class GameFoundEvent(wx.PyEvent):
    def __init__(self, data):
        """Init Result Event."""
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_GAMEFOUND_ID)
        self.data = data
        
class AnnouncedEvent(wx.PyEvent):
    def __init__(self, data):
        """Init Result Event."""
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_ANNOUNCED_ID)
        self.data = data
        
class AnnounceCompleteEvent(wx.PyEvent):
    def __init__(self, data):
        """Init Result Event."""
        wx.PyEvent.__init__(self)
        self.SetEventType(EVT_ANNOUNCECOMPLETE_ID)
        self.data = data

# Thread class that executes processing
class WorkerThread(Thread):
    """Worker Thread Class."""
    def __init__(self, notify_window, ifaceNum, iface):
        """Init Worker Thread Class."""
        Thread.__init__(self)
        self._notify_window = notify_window
        self._want_abort = 0
        self.setDaemon(1)
        
        self.ifaceNum = ifaceNum
        self.iface = iface
        self.runnerHandle = None
        self.pCappy = WC3PacketCapture(notify_window)
        
        # This starts the thread running on creation, but you could
        # also make the GUI thread responsible for calling this
        self.start()

    def start_capture(self):
        """ Start the network capture on the specified interface. """
        PHAND = ctypes.CFUNCTYPE(None,ctypes.POINTER(ctypes.c_ubyte),
                                 ctypes.POINTER(winpcapy.pcap_pkthdr),ctypes.POINTER(ctypes.c_ubyte))
        errbuf = ctypes.create_string_buffer(winpcapy.PCAP_ERRBUF_SIZE)

        interface = self.iface
        adhandle = winpcapy.pcap_open_live(interface.name,65536,
                                           winpcapy.PCAP_OPENFLAG_PROMISCUOUS,1000,errbuf)
        self.runnerHandle = adhandle
        
        print("Capturing on %s...\n" % (interface.description,))
        winpcapy.pcap_loop(self.runnerHandle, -1, PHAND(self.pCappy._packet_handler), None)
        
        if not self._want_abort:
            winpcapy.pcap_close(self.runnerHandle)
        #print "returning from start_capture"

    def run(self):
        self.start_capture()
        wx.PostEvent(self._notify_window, ResultEvent(None))

    def abort(self):
        """abort worker thread."""
        # Method for use by main thread to signal an abort
        self._want_abort = 1
        if self.runnerHandle:
            winpcapy.pcap_close(self.runnerHandle)
            self.runnerHandle = None

class MyStatusBar(wx.StatusBar):
    def __init__(self, parent):
        wx.StatusBar.__init__(self, parent, -1)

        # This status bar has three fields
        self.SetFieldsCount(3)
        # Sets the three fields to be relative widths to each other.
        self.SetStatusWidths([-2, -2, 150])
        #self.log = log
        self.sizeChanged = False
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_IDLE, self.OnIdle)

        # Field 0 ... just text
        self.SetStatusText("waiting", 0)

        # This will fall into field 1 (the second field)
        #self.g = wx.Gauge(self, -1, 100.0, (110, 50), (200, 25), style=wx.GA_SMOOTH)

        # set the initial position of the checkbox
        self.Reposition()

    #def setGauge(self, value):
    #    self.g.SetValue(value)

    def OnSize(self, evt):
        self.Reposition()  # for normal size events

        # Set a flag so the idle time handler will also do the repositioning.
        # It is done this way to get around a buglet where GetFieldRect is not
        # accurate during the EVT_SIZE resulting from a frame maximize.
        self.sizeChanged = True

    def OnIdle(self, evt):
        if self.sizeChanged:
            self.Reposition()

    # reposition the checkbox
    def Reposition(self):
        rect = self.GetFieldRect(1)
        #self.g.SetPosition((rect.x+2, rect.y+2))
        #self.g.SetSize((rect.width-4, rect.height-4))
        self.sizeChanged = False

class Frame1(wx.Frame):
    def _init_coll_gridBagSizer1_Growables(self, parent):
        # generated method, don't edit

        parent.AddGrowableRow(0)
        parent.AddGrowableCol(0)

    def _init_coll_gridBagSizer1_Items(self, parent):
        # generated method, don't edit

        parent.AddWindow(self.leftpanel, (0, 0), border=2,
              flag=wx.EXPAND | wx.ALL | wx.GROW, span=(1, 1))
        parent.AddWindow(self.rightpanel, (0, 1), border=0, flag=wx.ALIGN_RIGHT,
              span=(1, 1))
        parent.AddWindow(self.tlog, (1, 0), border=2,
              flag=wx.ALIGN_TOP | wx.EXPAND | wx.ALL, span=(1, 1))

    def _init_coll_mainSizer_Items(self, parent):
        # generated method, don't edit

        parent.AddSizer(self.gridBagSizer1, 1, border=0,
              flag=wx.ALL | wx.EXPAND)

    def _init_coll_outerNotebookSizer_Items(self, parent):
        # generated method, don't edit

        parent.AddWindow(self.notebook, 1, border=0, flag=wx.EXPAND | wx.ALL)

    def _init_sizers(self):
        # generated method, don't edit
        self.gridBagSizer1 = wx.GridBagSizer(hgap=5, vgap=5)
        self.gridBagSizer1.SetCols(2)
        self.gridBagSizer1.SetRows(2)
        self.gridBagSizer1.SetFlexibleDirection(wx.BOTH)
        self.gridBagSizer1.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)

        self.mainSizer = wx.BoxSizer(orient=wx.VERTICAL)

        self.outerNotebookSizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        self.outerNotebookSizer.SetMinSize(wx.Size(711, 591))

        self._init_coll_gridBagSizer1_Items(self.gridBagSizer1)
        self._init_coll_gridBagSizer1_Growables(self.gridBagSizer1)
        self._init_coll_mainSizer_Items(self.mainSizer)
        self._init_coll_outerNotebookSizer_Items(self.outerNotebookSizer)

        self.leftpanel.SetSizer(self.outerNotebookSizer)
        self.mainpanel.SetSizer(self.mainSizer)

    def _init_ctrls(self, prnt):
        # generated method, don't edit
        wx.Frame.__init__(self, id=wxID_FRAME1, name='', parent=prnt,
              pos=wx.Point(614, 71), size=wx.Size(1182, 951),
              style=wx.DEFAULT_FRAME_STYLE, title=u'WC3GameFilter')
        self.SetClientSize(wx.Size(1166, 913))
        self.SetMinSize(wx.Size(1182, 951))
        self.Bind(wx.EVT_CLOSE, self.OnFrame1Close)
        self.Bind(wx.EVT_IDLE, self.OnFrame1Idle)

        self.mainpanel = wx.Panel(id=wxID_FRAME1MAINPANEL, name=u'mainpanel',
              parent=self, pos=wx.Point(0, 0), size=wx.Size(1166, 913),
              style=wx.TAB_TRAVERSAL)

        self.rightpanel = wx.Panel(id=wxID_FRAME1RIGHTPANEL, name=u'rightpanel',
              parent=self.mainpanel, pos=wx.Point(919, 0), size=wx.Size(250,
              451), style=wx.TAB_TRAVERSAL)
        self.rightpanel.SetConstraints(LayoutAnchors(self.rightpanel, False,
              True, True, False))
        self.rightpanel.SetMinSize(wx.Size(250, 451))
        self.rightpanel.SetMaxSize(wx.Size(250, 451))
        self.rightpanel.SetLabel(u'rightpanel')
        self.rightpanel.SetAutoLayout(True)

        self.leftpanel = wx.Panel(id=wxID_FRAME1LEFTPANEL, name=u'leftpanel',
              parent=self.mainpanel, pos=wx.Point(2, 2), size=wx.Size(910, 735),
              style=wx.TAB_TRAVERSAL)

        self.notebook = wx.Notebook(id=wxID_FRAME1NOTEBOOK, name=u'notebook',
              parent=self.leftpanel, pos=wx.Point(0, 0), size=wx.Size(910, 735),
              style=0)
        self.notebook.SetAutoLayout(True)
        self.notebook.SetMinSize(wx.Size(-1, -1))
        self.notebook.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED,
              self.OnNotebookNotebookPageChanged, id=wxID_FRAME1NOTEBOOK)

        self.fullListPanel = wx.Panel(id=wxID_FRAME1FULLLISTPANEL,
              name=u'fullListPanel', parent=self.notebook, pos=wx.Point(0, 0),
              size=wx.Size(863, 567), style=wx.TAB_TRAVERSAL)

        self.filteredListPanel = wx.Panel(id=wxID_FRAME1FILTEREDLISTPANEL,
              name=u'filteredListPanel', parent=self.notebook, pos=wx.Point(0,
              0), size=wx.Size(863, 567), style=wx.TAB_TRAVERSAL)

        self.captureBox = wx.StaticBox(id=wxID_FRAME1CAPTUREBOX,
              label=u'Capturing', name=u'captureBox', parent=self.rightpanel,
              pos=wx.Point(8, 9), size=wx.Size(232, 96), style=0)

        self.bstart = wx.Button(id=wxID_FRAME1BSTART, label=u'Start',
              name=u'bstart', parent=self.rightpanel, pos=wx.Point(15, 66),
              size=wx.Size(97, 30), style=0)
        self.bstart.Enable(False)
        self.bstart.Bind(wx.EVT_BUTTON, self.OnBstartButton,
              id=wxID_FRAME1BSTART)

        self.bstop = wx.Button(id=wxID_FRAME1BSTOP, label=u'Stop',
              name=u'bstop', parent=self.rightpanel, pos=wx.Point(136, 66),
              size=wx.Size(97, 30), style=0)
        self.bstop.Enable(False)
        self.bstop.Bind(wx.EVT_BUTTON, self.OnBstopButton, id=wxID_FRAME1BSTOP)

        self.tlog = wx.TextCtrl(id=wxID_FRAME1TLOG, name=u'tlog',
              parent=self.mainpanel, pos=wx.Point(2, 746), size=wx.Size(910,
              165), style=wx.TE_MULTILINE, value=u'')

        self.liface = wx.Choice(choices=[], id=wxID_FRAME1LIFACE,
              name=u'liface', parent=self.rightpanel, pos=wx.Point(16, 33),
              size=wx.Size(216, 21), style=0)
        self.liface.Bind(wx.EVT_CHOICE, self.OnLifaceChoice,
              id=wxID_FRAME1LIFACE)

        self.filterBox = wx.StaticBox(id=wxID_FRAME1FILTERBOX,
              label=u'Filtering', name=u'filterBox', parent=self.rightpanel,
              pos=wx.Point(8, 113), size=wx.Size(232, 167), style=0)

        self.tfilter = wx.TextCtrl(id=wxID_FRAME1TFILTER, name=u'tfilter',
              parent=self.rightpanel, pos=wx.Point(17, 164), size=wx.Size(136,
              21), style=wx.TE_PROCESS_TAB | wx.TE_PROCESS_ENTER, value=u'')
        self.tfilter.Enable(False)
        self.tfilter.Bind(wx.EVT_TEXT_ENTER, self.OnTfilterTextEnter,
              id=wxID_FRAME1TFILTER)
        self.tfilter.Bind(wx.EVT_KEY_UP, self.OnTfilterKeyUp)

        self.bfilter = wx.Button(id=wxID_FRAME1BFILTER, label=u'filter',
              name=u'bfilter', parent=self.rightpanel, pos=wx.Point(158, 163),
              size=wx.Size(75, 23), style=0)
        self.bfilter.Enable(False)
        self.bfilter.Bind(wx.EVT_BUTTON, self.OnBfilterButton,
              id=wxID_FRAME1BFILTER)

        self.cbsound = wx.CheckBox(id=wxID_FRAME1CBSOUND,
              label=u'play sound on match', name=u'cbsound',
              parent=self.rightpanel, pos=wx.Point(16, 193), size=wx.Size(168,
              13), style=0)
        self.cbsound.SetValue(False)
        self.cbsound.Enable(True)
        self.cbsound.Bind(wx.EVT_CHECKBOX, self.OnCbsoundCheckbox,
              id=wxID_FRAME1CBSOUND)

        self.bclearMainList = wx.Button(id=wxID_FRAME1BCLEARMAINLIST,
              label=u'Clear Main list', name=u'bclearMainList',
              parent=self.rightpanel, pos=wx.Point(16, 248), size=wx.Size(97,
              23), style=0)
        self.bclearMainList.Bind(wx.EVT_BUTTON, self.OnBclearMainListButton,
              id=wxID_FRAME1BCLEARMAINLIST)

        self.bclearFilterList = wx.Button(id=wxID_FRAME1BCLEARFILTERLIST,
              label=u'Clear Filtered list', name=u'bclearFilterList',
              parent=self.rightpanel, pos=wx.Point(136, 248), size=wx.Size(97,
              23), style=0)
        self.bclearFilterList.Enable(False)
        self.bclearFilterList.Bind(wx.EVT_BUTTON, self.OnBclearFilterListButton,
              id=wxID_FRAME1BCLEARFILTERLIST)

        self.tcurrentFilteredGame = wx.TextCtrl(id=wxID_FRAME1TCURRENTFILTEREDGAME,
              name=u'tcurrentFilteredGame', parent=self.rightpanel,
              pos=wx.Point(8, 424), size=wx.Size(160, 21), style=wx.TE_READONLY,
              value=u'')
        self.tcurrentFilteredGame.Enable(False)

        self.bcopyFilter = wx.Button(id=wxID_FRAME1BCOPYFILTER, label=u'Copy',
              name=u'bcopyFilter', parent=self.rightpanel, pos=wx.Point(176,
              423), size=wx.Size(67, 23), style=0)
        self.bcopyFilter.Enable(False)
        self.bcopyFilter.Bind(wx.EVT_BUTTON, self.OnBcopyFilterButton,
              id=wxID_FRAME1BCOPYFILTER)

        self.cbClearListOnFilter = wx.CheckBox(id=wxID_FRAME1CBCLEARLISTONFILTER,
              label=u'Clear filtered list on pressing "filter"',
              name=u'cbClearListOnFilter', parent=self.rightpanel,
              pos=wx.Point(16, 211), size=wx.Size(216, 13), style=0)
        self.cbClearListOnFilter.SetValue(False)
        self.cbClearListOnFilter.Bind(wx.EVT_CHECKBOX,
              self.OnCbClearListOnFilterCheckbox,
              id=wxID_FRAME1CBCLEARLISTONFILTER)

        self.cbClipboardNewestFound = wx.CheckBox(id=wxID_FRAME1CBCLIPBOARDNEWESTFOUND,
              label=u'Copy newest found match to clipboard',
              name=u'cbClipboardNewestFound', parent=self.rightpanel,
              pos=wx.Point(16, 229), size=wx.Size(224, 13), style=0)
        self.cbClipboardNewestFound.SetValue(True)
        self.cbClipboardNewestFound.Bind(wx.EVT_CHECKBOX,
              self.OnCbClipboardNewestFoundCheckbox,
              id=wxID_FRAME1CBCLIPBOARDNEWESTFOUND)

        self.staticText1 = wx.StaticText(id=wxID_FRAME1STATICTEXT1,
              label=u'Newest found:', name='staticText1',
              parent=self.rightpanel, pos=wx.Point(8, 408), size=wx.Size(72,
              13), style=0)

        self.limatchType = wx.Choice(choices=[], id=wxID_FRAME1LIMATCHTYPE,
              name=u'limatchType', parent=self.rightpanel, pos=wx.Point(17,
              132), size=wx.Size(215, 21), style=0)
        self.limatchType.Bind(wx.EVT_CHOICE, self.OnLimatchTypeChoice,
              id=wxID_FRAME1LIMATCHTYPE)

        self.staticBox1 = wx.StaticBox(id=wxID_FRAME1STATICBOX1, label=u'Misc',
              name='staticBox1', parent=self.rightpanel, pos=wx.Point(8, 288),
              size=wx.Size(232, 80), style=0)

        self.cbclearOldGames = wx.CheckBox(id=wxID_FRAME1CBCLEAROLDGAMES,
              label=u'automatically clear games older than',
              name=u'cbclearOldGames', parent=self.rightpanel, pos=wx.Point(16,
              312), size=wx.Size(208, 16), style=0)
        self.cbclearOldGames.SetValue(False)
        self.cbclearOldGames.Bind(wx.EVT_CHECKBOX,
              self.OnCbclearOldGamesCheckbox, id=wxID_FRAME1CBCLEAROLDGAMES)

        self.toldGameAge = wx.TextCtrl(id=wxID_FRAME1TOLDGAMEAGE,
              name=u'toldGameAge', parent=self.rightpanel, pos=wx.Point(16,
              333), size=wx.Size(32, 21),
              style=wx.TE_PROCESS_TAB | wx.TE_PROCESS_ENTER | wx.TE_RIGHT,
              value=u'15')
        self.toldGameAge.Bind(wx.EVT_TEXT_ENTER, self.OnToldGameAgeTextEnter,
              id=wxID_FRAME1TOLDGAMEAGE)
        self.toldGameAge.Bind(wx.EVT_TEXT, self.OnToldGameAgeText,
              id=wxID_FRAME1TOLDGAMEAGE)

        self.staticText2 = wx.StaticText(id=wxID_FRAME1STATICTEXT2,
              label=u'minutes', name='staticText2', parent=self.rightpanel,
              pos=wx.Point(56, 336), size=wx.Size(38, 13), style=0)

        self._init_sizers()

    def init_coll_notebook_Pages(self, parent):
        parent.AddPage(imageId=-1, page=self.fullListPanel, select=True,
              text=u'All Games')
              
        parent.AddPage(imageId=-1, page=self.filteredListPanel, select=False,
              text=u'Filtered Games')

    def init_coll_innerNotebookSizer_Items(self, parent):
        parent.AddWindow(self.fullList, 1, border=10, flag=wx.EXPAND | wx.ALL)
        
    def init_coll_filteredListSizer_Items(self, parent):
        parent.AddWindow(self.filteredList, 1, border=10, flag=wx.EXPAND | wx.ALL)

    def __init__(self, parent):
        self.nbPagesInitialized = False
        
        self._init_ctrls(parent)
        self.Center()
        
        self.fullList = ListCtrlPanel(self.fullListPanel, self.tlog)
        self.filteredList = ListCtrlPanel(self.filteredListPanel, self.tlog)

        self.init_coll_notebook_Pages(self.notebook)
        self.notebook.GetPage(0).list = self.fullList
        self.notebook.GetPage(1).list = self.filteredList
        
        self.nbPagesInitialized = True
        
        self.innerNotebookSizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        self.fullListPanel.SetSizer(self.innerNotebookSizer)
        self.init_coll_innerNotebookSizer_Items(self.innerNotebookSizer)
        
        self.filteredListSizer = wx.BoxSizer(orient=wx.HORIZONTAL)
        self.filteredListPanel.SetSizer(self.filteredListSizer)
        self.init_coll_filteredListSizer_Items(self.filteredListSizer)
        
        # manually fix the borders of the boxsizer, cause that somehow doesnt work out too well
        self.fullList.SetSize(self.fullListPanel.GetSize() - wx.Size(20, 20))
        self.fullList.SetPosition(wx.Point(10, 10))
        
        self.filteredList.SetSize(self.filteredListPanel.GetSize() - wx.Size(20, 20))
        self.filteredList.SetPosition(wx.Point(10, 10))
        
        self.statusBar = MyStatusBar(parent=self)
        self.SetStatusBar(self.statusBar)
        
        self.version = u"1.0.1c"
        self.SetTitle(u"WC3GameFilter %s" % self.version)
    
        
        # bootstrap
        frozen = False
        if hasattr(sys, 'frozen'):
            pathBase = sys.executable
            frozen = True

        else:
            pathBase = sys.argv[0]
        path = os.path.dirname(os.path.abspath(sys.argv[0]))
        if frozen:
            # redirect sys.stderr
            f = open(os.path.join(path, "error.log"), "wb+")
            sys.stderr = f
        
        # mixer for sounds
        snd = os.path.join(path, "sounds", "mktoasty.mp3")
        self.sound = mp3play.load(snd)
        
        # icon
        ib = wx.IconBundle()
        ib.AddIconFromFile(os.path.join(path, "icon.ico"), wx.BITMAP_TYPE_ICO)
        self.SetIcons(ib)    
                
        # bind custom events
        EVT_RESULT(self, self.workerEnded)
        EVT_GAMEFOUND(self, self.gameFound)
        EVT_ANNOUNCED(self, self.announced)
        EVT_ANNOUNCECOMPLETE(self, self.announceComplete)
        
        # misc
        self.workers = []
        self.logger = Logger(self.tlog)
        self.tlog.SetValue(u"waiting\n")        
        self.ifaceNum = None
        self.iface = None
        self.ifaces = []
        self.maxID = 0
        self.announceTime = None
        self.announceTimeDT = None
        self.gameFilter = None
        self.timeFormat = "%d.%m.%y %H:%M:%S"
        self.matchType = None
        self.newestFoundGameToClipboard = True
        self.newestFilteredGameSeen = None
        self.clearListOnFilter = False
        self.clearOldGames = True
        self.defaultGameIsOldAfter = 30
        self.gameIsOldAfter = self.defaultGameIsOldAfter # minutes
        self.lastCheckForOldGames = None
        self.checkForOldGamesInterval = 30 # seconds
        self.playSoundOnMatch = True
        
        self.games = dict()
        self.gameLowerToGame = dict()
        self.filteredFound = 0
        self.gameCount = 0
        
        self.matchTypes = (
            ("Match All (whole string)", "match_matchAll"),
            ("Match Any (any word)", "match_matchAny"),
            ("Match Regexp", "match_matchRegexp"),
        )
                
        # set match types
        for desc, func in self.matchTypes:
            self.limatchType.Append(desc)
            
        # get config
        self.loadConfig()            
            
        # initialize GUI parts based on config
        self.matchType = self.matchType or 0
        self.limatchType.Select(self.matchType)
        self.cbClipboardNewestFound.SetValue(self.newestFoundGameToClipboard)
        self.cbClearListOnFilter.SetValue(self.clearListOnFilter)
        self.cbclearOldGames.SetValue(self.clearOldGames)
        self.toldGameAge.SetValue(str(self.gameIsOldAfter))
        self.cbsound.SetValue(self.playSoundOnMatch)
        
        # get interfaces
        for iface in WC3PacketCapture.get_interfaces():
            self.liface.Append(iface.description)
            self.ifaces.append(iface)
            
        if len(self.ifaces) == 1:
            self.ifaceNum = 0
            
        if self.ifaceNum != None:
            # set default iface num
            self.chooseIFace(self.ifaceNum)
            self.liface.Select(self.ifaceNum)
            
        self.setStatusGames()
        
    def loadConfig(self):
        r = WindowsRegistry(company=None, project="WC3GameFilter", write=1)
        #self.clearFNCRC = r.pget("clearCRC") or True
        self.ifaceNum = r.pget("iface") or None
        self.matchType = r.pget("matchType") or None
        self.newestFoundGameToClipboard = r.pget("newestFoundGameToClipboard")
        self.newestFoundGameToClipboard = True if self.newestFoundGameToClipboard is None else self.newestFoundGameToClipboard
        self.clearListOnFilter = r.pget("clearListOnFilter")
        self.clearListOnFilter = False if self.clearListOnFilter is None else self.clearListOnFilter
        self.clearOldGames = r.pget("clearOldGames")
        self.clearOldGames = True if self.clearOldGames is None else self.clearOldGames
        self.gameIsOldAfter = r.pget("gameIsOldAfter") or self.defaultGameIsOldAfter
        self.playSoundOnMatch = r.pget("playSoundOnMatch")
        self.playSoundOnMatch = True if self.playSoundOnMatch is None else self.playSoundOnMatch
        r.close()

    def writeConfig(self):
        r = WindowsRegistry(company=None, project="WC3GameFilter", write=1)
        #r.pset("clearCRC", self.clearFNCRC)
        r.pset("iface", self.ifaceNum)
        r.pset("matchType", self.matchType)
        r.pset("newestFoundGameToClipboard", self.newestFoundGameToClipboard)
        r.pset("clearListOnFilter", self.clearListOnFilter)
        r.pset("clearOldGames", self.clearOldGames)
        r.pset("gameIsOldAfter", self.gameIsOldAfter)
        r.pset("playSoundOnMatch", self.playSoundOnMatch)
        r.close()
        
    def playSound(self):
        self.sound.play()
        time.sleep(min(30, self.sound.seconds()))
        self.sound.stop()
        
    def setStatus(self, val, col=0):
        self.statusBar.SetStatusText(val, col)
        
    def setStatusGames(self):
        self.statusBar.SetStatusText(u"%s games" % self.gameCount, 2)
        self.notebook.SetPageText(0, u"All Games (%s)" % str(self.gameCount))
        
    def clearList(self, list):
        list.list.DeleteAllItems()
        list.itemDataMap = {}
        list.gameMap = {}

    def OnBstartButton(self, event):
        if not self.workers and self.iface:
            #cnt = 0
            #self.workers.append(WorkerThread(self, 2, PacketCapture.get_interfaces()[2]))
            #for iface in WC3PacketCapture.get_interfaces():
            #    self.workers.append(WorkerThread(self, cnt, iface))
            #    cnt += 1
            self.workers.append(WorkerThread(self, self.ifaceNum, self.iface))
                
        self.setStatus("running")
        self.statusBar.SetStatusText(u"Capturing: %s" % self.iface.description, 1)
        self.toggleControls(CAPTURE_STARTED)
        event.Skip()

    def stopWorkers(self):
        if self.workers:
            for worker in self.workers:
                worker.abort()
                self.workers.remove(worker)

    def OnBstopButton(self, event):
        self.stopWorkers()
        self.toggleControls(CAPTURE_STOPPED)
        self.setStatus("waiting")
        self.statusBar.SetStatusText(u"Not capturing", 1)
        event.Skip()

    def OnFrame1Close(self, event):
        self.stopWorkers()
        event.Skip()
        
    def gameMatches(self, game, needle):
        name, func = self.matchTypes[self.matchType]
        
        return getattr(self, func)(game, needle)
        
    def match_matchAll(self, str, needle):
        return needle.lower() in str.lower()
    
    def match_matchAny(self, str, needle):
        return bool([n for n in needle.lower().split() if n in str.lower()])
    
    def match_matchRegexp(self, str, needle):
        return re.search(needle, str, re.UNICODE | re.DOTALL | re.IGNORECASE)
        
    def announced(self, event):
        self.announceTime = time.time()
        self.announceTimeDT = datetime.datetime.fromtimestamp(self.announceTime)
        self.setStatus("receiving announce")
        self.lastAnnounceNewGames = 0
        self.lastAnnounceFoundGames = 0
        
    def toClipboard(self, val):
        clipdata = wx.TextDataObject()
        clipdata.SetText(val)
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(clipdata)
        wx.TheClipboard.Close()
        
    def gameToClipboard(self, game, timestamp):
        if not self.newestFilteredGameSeen or timestamp > self.newestFilteredGameSeen:
            self.tcurrentFilteredGame.SetValue(game)
            self.toClipboard(game)
            
            print u"Copied newest filtered game to clipboard: %s" % game
            self.newestFilteredGameSeen = timestamp
        
    def gameFound(self, event):
        now = self.announceTime
        timeString = self.announceTimeDT.strftime(self.timeFormat)
        gameData = event.data

        game, freeSlots = gameData.GameName.decode("utf-8", 'replace'), gameData.FreeSlots

        ID, lastFreeSlots, sCount, lastSeen = self.games.get(game, (0, 0, 0, now))

        # generate new ID if game unknown
        if not sCount:
            self.maxID+=1
            ID = self.maxID
            self.lastAnnounceNewGames += 1
            
        if self.fullList.insertItem((ID, game, freeSlots, sCount+1, now), timeString):
            if self.gameFilter and self.gameMatches(game, self.gameFilter):
                self.filteredList.insertItem((ID, game, freeSlots, sCount+1, now), timeString)
                   
                if not sCount:
                    print u"Found wanted game: %s" % game
                    self.filteredFound += 1
                    self.updateFNotebookTitle()
                    self.lastAnnounceFoundGames += 1
                    
                    if self.newestFoundGameToClipboard:
                        self.gameToClipboard(game, now)
                
            self.games[game] = (ID, freeSlots, sCount+1, now)
            self.gameLowerToGame[game.lower()] = game
            
    def announceComplete(self, event):
        if self.clearOldGames:
            self.doClearOldGames()
        
        # sort list
        gamesInAnnounce = event.data
        #self.tlog.AppendText("." * gamesInAnnounce)
        #self.fullList.SortListItems(*self.fullList.GetSortState())
        self.fullList.SortListItems()
        self.filteredList.SortListItems()
        for name in ("fullList", "filteredList"):
            list = getattr(self, name)
            list.Update()
            list.list.Update()
            list.Refresh()
            list.list.Refresh()
        
        if self.lastAnnounceFoundGames and self.playSoundOnMatch:
            # play sound
            self.playSound()

        self.gameCount += self.lastAnnounceNewGames
        self.setStatus("announce completed, %s games found (%s new)" % (gamesInAnnounce, self.lastAnnounceNewGames))
        self.setStatusGames()
        
    def doClearOldGames(self):
        t = time.time()
        clearedOldGames = 0
        if not self.lastCheckForOldGames or t > self.lastCheckForOldGames + self.checkForOldGamesInterval:
            g = self.games.copy()
            for game, data in g.iteritems():
                ID, slots, seen, timestamp = data
                if t >= timestamp + self.gameIsOldAfter * 60:
                    del(self.games[game])
                    del(self.gameLowerToGame[game.lower()])
                    self.fullList.removeItem(ID)
                    if self.filteredList.removeItem(ID):
                        self.filteredFound -= 1
                        
                    self.gameCount -= 1                    
                    clearedOldGames += 1
            
            if clearedOldGames:
                print "Cleared %s games older than %s minutes" % (clearedOldGames, self.gameIsOldAfter)
                self.updateFNotebookTitle()
            self.lastCheckForOldGames = t
        
    def updateFNotebookTitle(self):
        if self.gameFilter:
            self.notebook.SetPageText(1, u"Filtered Games: %s (%s)" % (self.gameFilter, str(self.filteredFound)))
        else:
            self.notebook.SetPageText(1, u"Filtered Games")
        
    def workerEnded(self, event):
        pass
    
    def toggleControls(self, t, inverse=False):
        for a, b in controlGroups.get(t, []):
            if a:
                ctl, state = a, b
            else:
                self.toggleControls(b, True)
                return
            
            getattr(self, ctl).Enable(state if not inverse else not state)
    
    def chooseIFace(self, inum):
        self.ifaceNum = inum
        self.iface = self.ifaces[inum]
        self.toggleControls(IFACE_SELECTED)

    def OnLifaceChoice(self, event):
        self.chooseIFace(event.Selection)
        self.writeConfig()
        event.Skip()

    def clearFilteredList(self):
        self.clearList(self.filteredList)

    def OnBclearMainListButton(self, event):
        self.clearList(self.fullList)
        self.games = {}
        self.gameLowerToGame = {}
        self.gameCount = 0
        self.setStatusGames()
        event.Skip()

    def OnBfilterButton(self, event, skip=True):
        self.gameFilter = self.tfilter.GetValue()
        self.gameFilter = self.gameFilter if len(self.gameFilter) > 0 else None
        
        if self.clearListOnFilter:
            self.clearFilteredList()
            self.filteredFound = 0
        
        if not self.gameFilter:
            self.updateFNotebookTitle()
            return
        
        self.toggleControls(FILTER_ENTERED)
        
        # filter currently known games, too
        for game, ID in self.fullList.gameMap.iteritems():
            if self.gameMatches(game, self.gameFilter):
                data = self.fullList.itemDataMap.get(ID, None)
                if data:
                    name, slots, count, timestamp = data
                    tString = datetime.datetime.fromtimestamp(timestamp).strftime(self.timeFormat)
                    self.filteredList.insertItem((ID, self.gameLowerToGame[name], slots, count, timestamp), tString)
                    self.filteredFound += 1
                    
                    if self.newestFoundGameToClipboard:
                        self.gameToClipboard(self.gameLowerToGame[name], timestamp)
                    
        self.updateFNotebookTitle()
        self.filteredList.SortListItems()
        skip and event.Skip()

    def OnNotebookNotebookPageChanged(self, event):
        if self.nbPagesInitialized:
            list = self.notebook.GetPage(event.GetSelection()).list
            list.isDirty = True

        event.Skip()

    def OnBclearFilterListButton(self, event):
        self.clearFilteredList()
        self.filteredFound = 0
        self.updateFNotebookTitle()
        event.Skip()

    def OnTfilterTextEnter(self, event):
        self.OnBfilterButton(event, False)

    def OnTfilterKeyUp(self, event):
        event.Skip()

    def OnFrame1Idle(self, event):
        for name in ("fullList", "filteredList"):
            list = getattr(self, name)
            if getattr(list, "isDirty", False):
                # when a notebook tab has been changed the listctrls sometimes magically get distorted 
                # workaround for that problem on dirty:
                list.Update()
                list.list.Update()
                list.Refresh()
                list.list.Refresh()
                list.isDirty = False
        event.Skip()

    def OnLimatchTypeChoice(self, event):
        self.matchType = event.GetSelection()
        self.writeConfig()
        event.Skip()

    def OnCbClipboardNewestFoundCheckbox(self, event):
        self.newestFoundGameToClipboard = event.Checked()
        self.writeConfig()
        event.Skip()

    def OnBcopyFilterButton(self, event):
        val = self.tcurrentFilteredGame.GetValue().strip()
        if len(val):
            self.toClipboard(val)
        event.Skip()

    def OnCbClearListOnFilterCheckbox(self, event):
        self.clearListOnFilter = event.Checked()
        self.writeConfig()
        event.Skip()

    def _validateIsOldValue(self, val):
        try:
            val = int(val)
        except ValueError:
            return 15
        return val

    def OnCbclearOldGamesCheckbox(self, event):
        self.clearOldGames = event.Checked()
        if self.clearOldGames:
            self.gameIsOldAfter = self._validateIsOldValue(self.toldGameAge.GetValue())
            
        self.writeConfig()
        event.Skip()

    def _gameAgeTextChanged(self):
        self.gameIsOldAfter = self._validateIsOldValue(self.toldGameAge.GetValue())
        self.writeConfig()

    def OnToldGameAgeTextEnter(self, event):
        self._gameAgeTextChanged()

    def OnToldGameAgeText(self, event):
        self._gameAgeTextChanged()
        event.Skip()

    def OnCbsoundCheckbox(self, event):
        self.playSoundOnMatch = event.Checked()
        self.writeConfig()
        event.Skip()

